package com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl;

import com.p7.architect.store.shoppingcart.domain.shared.Aggregate;
import com.p7.architect.store.shoppingcart.domain.shared.Repository;
import com.p7.architect.store.shoppingcart.domain.shared.ValueObject;
import com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl.diff.AggregateManager;
import com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl.diff.AggregateManagerFactory;
import com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl.diff.EntityDiff;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Slf4j
public abstract class RepositorySupport<T extends Aggregate<ID>, ID extends ValueObject>
        implements Repository<T, ID> {

    private final AggregateManager<T, ID> aggregateManager;

    protected RepositorySupport(Class<T> targetClass) {
        this.aggregateManager = AggregateManagerFactory.newInstance(targetClass);
    }

    @Override
    public void attach(@NotNull T aggregate) {
        this.aggregateManager.attach(aggregate);
    }

    @Override
    public void detach(@NotNull T aggregate) {
        this.aggregateManager.detach(aggregate);
    }

    @Override
    public T find(@NotNull ID id) {
        T aggregate = this.onSelect(id);
        if (aggregate != null) {
            this.attach(aggregate);
        }
        return aggregate;
    }

    @Override
    public void remove(@NotNull T aggregate) {
        EntityDiff diff = getEntityDiff(aggregate);
        if (diff == null) {
            return;
        }

        this.onDelete(aggregate, diff);

        T t = this.onSelect(aggregate.getId());
        if (null == t) {
            this.detach(aggregate);
            log.info("RepositorySupport#remove detach aggregate success");
            return;
        }
        aggregateManager.merge(t);
    }

    @Nullable
    private EntityDiff getEntityDiff(@NotNull T aggregate) {
        EntityDiff diff = null;
        try {
            diff = aggregateManager.detectChanges(aggregate);
        } catch (IllegalAccessException e) {
            log.error("DbRepositorySupport#save failure, error: {}", e);
        }

        if (diff == null || diff.equals(EntityDiff.EMPTY)) {
            log.info("DbRepositorySupport#save diff is null|empty, aggregateId: {}", aggregate.getId());
            return null;
        }
        return diff;
    }

    @Override
    public ID save(@NotNull T aggregate) {
        if (aggregate.getId() == null) {
            ID id = this.onInsert(aggregate);
            this.attach(aggregate);
            log.info("DbRepositorySupport#save onInsert is success.");
            return id;
        }

        EntityDiff diff = getEntityDiff(aggregate);
        if (diff == null || diff.getDiffs().size() == 0) {
            return aggregate.getId();
        }

        this.onUpdate(aggregate, diff);

        aggregateManager.merge(aggregate);
        return aggregate.getId();
    }

    protected abstract ID onInsert(T aggregate);

    protected abstract T onSelect(ID id);

    protected abstract void onUpdate(T aggregate, EntityDiff diff);

    protected abstract void onDelete(T aggregate, EntityDiff entityDiff);

}